package generator;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.stream.StreamResult;

import org.xml.sax.InputSource;

import model.Annotation;
import model.Choice;
import model.Element;
import model.EnclosingType;
import model.Not;
import model.Sibling;
import model.Twin;

public class SchemeGenerator {
	
	public String generateXmlSchema(Annotation annotation){
		String unformattedXml = generatePattern(annotation);
		return formatXML(unformattedXml);
	}
	
	public String generatePattern(Annotation annotation){
		
		String context = generateContext(annotation);
		List<String> asserts = generateAsserts(annotation);
		
		StringBuilder schema = new StringBuilder("<?xml version='1.0' encoding='utf-8'?>");
		schema.append("<schema xmlns=\"http://purl.oclc.org/dsdl/schematron\">");
		schema.append("<pattern name=\"Test\">");
		schema.append("<rule context=\""+context+"\">");
		for (String string : asserts) {
			schema.append(string);
		}
		schema.append("</rule>");
		schema.append("</pattern>");
		schema.append("</schema>");
		
		return schema.toString();
	}

	private String generateContext(Annotation annotation) {
		return "//annotation[@name='"+annotation.getName()+"']";
	}
	
	private List<String> generateAsserts(Annotation annotation) {
		List<String> asserts = new ArrayList<>();
		
		if(annotation.getMinOccurs()>0){
//			String errorMessage = "minOccurs";
//			StringBuilder assertBuilder = new StringBuilder();
//			assertBuilder.append("<assert test=\"");
//			assertBuilder.append("@name='A' and ");
//			assertBuilder.append("count(//annotation[@name = '"+annotation.getName()+"']) &gt;= "+annotation.getMinOccurs());
//			assertBuilder.append("\">"+errorMessage+"</assert>");
//			asserts.add(assertBuilder.toString());
		}
		
		if(annotation.getMinParamCount()>0){
			String errorMessage = "Too few parameters"; 
			StringBuilder assertBuilder = new StringBuilder();
			assertBuilder.append("<assert test=\"");
			assertBuilder.append("count(../parameters/parameter) &gt;= "+annotation.getMinParamCount());
			assertBuilder.append("\">"+errorMessage+"</assert>");
			asserts.add(assertBuilder.toString());
		}
		
		if(annotation.getMaxParamCount()>0){
			String errorMessage = "Too many parameters!"; 
			StringBuilder assertBuilder = new StringBuilder();
			assertBuilder.append("<assert test=\"");
			assertBuilder.append("count(../parameters/parameter) &lt;= "+annotation.getMaxParamCount());
			assertBuilder.append("\">"+errorMessage+"</assert>");
			asserts.add(assertBuilder.toString());
		}
		
		if(annotation.getParamType()!=null){
			String errorMessage = "Not supported type of parameter!"; 
			StringBuilder assertBuilder = new StringBuilder();
			assertBuilder.append("<report test=\"");
			assertBuilder.append("../parameters/parameter/@type!='"+annotation.getParamType()+"'");
			assertBuilder.append("\">"+errorMessage+"</report>");
			asserts.add(assertBuilder.toString());
		}
		
		if(annotation.getReturnType()!=null){
			String errorMessage = "Not supported return type!"; 
			StringBuilder assertBuilder = new StringBuilder();
			assertBuilder.append("<report test=\"");
			assertBuilder.append("../@returnType!='"+annotation.getReturnType()+"'");
			assertBuilder.append("\">"+errorMessage+"</report>");
			asserts.add(assertBuilder.toString());
		}
		
		if(annotation.getFieldType()!=null){
			String errorMessage = "Not supported field type!"; 
			StringBuilder assertBuilder = new StringBuilder();
			assertBuilder.append("<report test=\"");
			assertBuilder.append("../@type!='"+annotation.getFieldType()+"'");
			assertBuilder.append("\">"+errorMessage+"</report>");
			asserts.add(assertBuilder.toString());
		}
		
		if(annotation.getPrefix()!=null){
			String errorMessage = "Bad prefix!"; 
			StringBuilder assertBuilder = new StringBuilder();
			assertBuilder.append("<assert test=\"");
			assertBuilder.append("starts-with(../@name, '"+annotation.getPrefix()+"')");
			assertBuilder.append("\">"+errorMessage+"</assert>");
			asserts.add(assertBuilder.toString());
		}
		
		if(annotation.getEnclosingType()!=null){
			EnclosingType enclosingType = annotation.getEnclosingType();
			List<model.Element> children = enclosingType.getChildren();
			for (Element element : children) {
				if (element instanceof Annotation) {
					String errorMessage = "Missing annotation on enclosing type!"; 
					StringBuilder assertBuilder = new StringBuilder();
					assertBuilder.append("<assert test=\"");
					assertBuilder.append("../../../type/annotation/@name='"+((Annotation)element).getName()+"'");
					assertBuilder.append("\">"+errorMessage+"</assert>");
					asserts.add(assertBuilder.toString());
				}
				else if (element instanceof Not) {
					String errorMessage = "Unsupported annotation on enclosing type!"; 
					StringBuilder assertBuilder = new StringBuilder();
					assertBuilder.append("<report test=\"");
					assertBuilder.append("../../../type/annotation/@name='"+((Not)element).getAnnotation().getName()+"'");
					assertBuilder.append("\">"+errorMessage+"</report>");
					asserts.add(assertBuilder.toString());
				}
				else if (element instanceof Choice) {
					String errorMessage = "Missing annotation on enclosing type!"; 
					StringBuilder assertBuilder = new StringBuilder();
					assertBuilder.append("<assert test=\"");
					Iterator<Annotation> iterator = Arrays.asList(((Choice)element).getAnnotations()).iterator();
					while (iterator.hasNext()) {
						assertBuilder.append("../../../type/annotation/@name='"+iterator.next().getName()+"'");
						if(iterator.hasNext())assertBuilder.append(" or ");
					}
					assertBuilder.append("\">"+errorMessage+"</assert>");
					asserts.add(assertBuilder.toString());
				}
			}
		}
		
		if(annotation.getSiblings()!=null){
			Sibling sibling = annotation.getSiblings();
			List<Element> siblings = sibling.getSiblings();
			for (Element element : siblings) {
				if (element instanceof Annotation) {
					String errorMessage = "Missing annotation on sibling methods!"; 
					StringBuilder assertBuilder = new StringBuilder();
					assertBuilder.append("<assert test=\"");
					assertBuilder.append("../../method/annotation/@name='"+((Annotation)element).getName()+"'");
					assertBuilder.append("\">"+errorMessage+"</assert>");
					asserts.add(assertBuilder.toString());
				}
				else if (element instanceof Not) {
					String errorMessage = "Unsupported annotation on sibling methods!"; 
					StringBuilder assertBuilder = new StringBuilder();
					assertBuilder.append("<report test=\"");
					assertBuilder.append("../../method/annotation/@name='"+((Not)element).getAnnotation().getName()+"'");
					assertBuilder.append("\">"+errorMessage+"</report>");
					asserts.add(assertBuilder.toString());
				}
				else if (element instanceof Choice) {
					String errorMessage = "Missing annotation on sibling methods!"; 
					StringBuilder assertBuilder = new StringBuilder();
					assertBuilder.append("<assert test=\"");
					Iterator<Annotation> iterator = Arrays.asList(((Choice)element).getAnnotations()).iterator();
					while (iterator.hasNext()) {
						assertBuilder.append("../../method/annotation/@name='"+iterator.next().getName()+"'");
						if(iterator.hasNext())assertBuilder.append(" or ");
					}
					assertBuilder.append("\">"+errorMessage+"</assert>");
					asserts.add(assertBuilder.toString());
				}
			}
		}
		
		if(annotation.getTwins()!=null){
			Twin twin = annotation.getTwins();
			List<Element> twins = twin.getTwins();
			for (Element element : twins) {
				if (element instanceof Annotation) {
					String errorMessage = "Missing twin annotation on element!"; 
					StringBuilder assertBuilder = new StringBuilder();
					assertBuilder.append("<assert test=\"");
					assertBuilder.append("../annotation/@name='"+((Annotation)element).getName()+"'");
					assertBuilder.append("\">"+errorMessage+"</assert>");
					asserts.add(assertBuilder.toString());
				}
				else if (element instanceof Not) {
					String errorMessage = "Unsupported twin annotation on element!"; 
					StringBuilder assertBuilder = new StringBuilder();
					assertBuilder.append("<report test=\"");
					assertBuilder.append("../annotation/@name='"+((Not)element).getAnnotation().getName()+"'");
					assertBuilder.append("\">"+errorMessage+"</report>");
					asserts.add(assertBuilder.toString());
				}
				else if (element instanceof Choice) {
					String errorMessage = "Missing twin annotation on element!"; 
					StringBuilder assertBuilder = new StringBuilder();
					assertBuilder.append("<assert test=\"");
					Iterator<Annotation> iterator = Arrays.asList(((Choice)element).getAnnotations()).iterator();
					while (iterator.hasNext()) {
						assertBuilder.append("../annotation/@name='"+iterator.next().getName()+"'");
						if(iterator.hasNext())assertBuilder.append(" or ");
					}
					assertBuilder.append("\">"+errorMessage+"</assert>");
					asserts.add(assertBuilder.toString());
				}
			}
		}
			
			
		return asserts;
	}
	
    public String formatXML(String unformattedXML) {
        try {
            Transformer serializer = SAXTransformerFactory.newInstance().newTransformer();
            serializer.setOutputProperty(OutputKeys.INDENT, "yes");
            serializer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
            Source xmlSource = new SAXSource(new InputSource(new ByteArrayInputStream(unformattedXML.getBytes())));
            StreamResult res = new StreamResult(new ByteArrayOutputStream());
            serializer.transform(xmlSource, res);
            return new String(((ByteArrayOutputStream) res.getOutputStream()).toByteArray());
        } catch (Exception e) {
            return unformattedXML;
        }
    }
	

}
